package com.cs157a.controller;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.BevelBorder;

import com.cs157a.dao.AplCar;
import com.cs157a.dao.AplCarDao;
import com.cs157a.dao.DBConnection;
import com.cs157a.dao.Make;
import com.cs157a.dao.MakeDao;
import com.cs157a.dao.NotFoundException;
import com.cs157a.dao.PartType;
import com.cs157a.dao.RdiPart;
import com.cs157a.dao.RdiPartDao;
import com.cs157a.dao.Radcrx;
import com.cs157a.dao.RadcrxDao;
import com.cs157a.util.UiUtil;
import com.cs157a.view.AposApplicationView;
import com.cs157a.view.AutoFilterPanel;
import com.cs157a.view.CommonString;
import com.cs157a.view.NewMakerDialog;
import com.cs157a.view.NewAutoModelDialog;
import com.cs157a.view.NewPartDialog;
import com.cs157a.view.PartDetailPanel;
import com.cs157a.view.PartSummaryPanel;
import com.cs157a.view.VendorType;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class is the main controller for the APOS application. It will handle
 * all the requests from the user thru the GUI-View, and get all data objects
 * from the DAO.
 * 
 * @author Hiep
 * 
 */
public class AposApplicationController {

	private MakeDao makeDao;
	private AplCarDao carDao;
	private RdiPartDao partDao;
	private RadcrxDao radDao;
	private DBConnection dbConnection;
	private Connection connection;
	private AposApplicationView applicationView;
	private NewMakerDialog newMakerDialog;
	private NewAutoModelDialog newAutoModelDialog;
	private NewPartDialog newPartDialog;
	private JDialog aboutDialog;
	private PartSummaryPanel partSummaryPanel;
	private PartDetailPanel partDetailPanel;
	private List<Make> allMakesCache;
	private AutoFilterPanel autoFilterPanel;

	private boolean selectingCar;
	private String selectedMake;
	private AplCar selectedCar;
	private String selectedVendor;

	public AposApplicationController() {
		dbConnection = new DBConnection();
		connection = dbConnection.getDBConnection();
		makeDao = new MakeDao();
		carDao = new AplCarDao();
		partDao = new RdiPartDao();
		radDao = new RadcrxDao();
	}

	public void setPartSummaryPanel(PartSummaryPanel partSummaryPanel) {
		this.partSummaryPanel = partSummaryPanel;
	}

	public void setPartDetailPanel(PartDetailPanel partDetailPanel) {
		this.partDetailPanel = partDetailPanel;
	}

	@SuppressWarnings("unchecked")
	public List<String> getAllMakeNames() {
		List<String> names = new ArrayList<String>();
		allMakesCache = new ArrayList<Make>();

		try {
			List<Object> all = makeDao.loadAll(connection);
			for (Object obj : all) {
				Make make = (Make) obj;
				allMakesCache.add(make);

				names.add(make.getName());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return names;
	}

	/**
	 * Create the GUI and show it. For thread safety, this method should be
	 * invoked from the event-dispatching thread.
	 */
	public void createAndShowGUI() {
		// Create and set up the window.
		applicationView = new AposApplicationView(this);
		applicationView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		applicationView.addWindowListener(new MainWindowListener());

		UiUtil.centerAndShow(applicationView);
	}

	public void exit() {
		dbConnection.disconnectFromDB();

		if (newMakerDialog != null) {
			newMakerDialog.dispose();
		}

		if (newPartDialog != null) {
			newPartDialog.dispose();
		}

		if (aboutDialog != null) {
			aboutDialog.dispose();
		}

		applicationView.cleanUp();
	}

	public static void main(String[] args) {
		AposApplicationController controller = new AposApplicationController();
		controller.createAndShowGUI();

	}

	public void aboutPerformed() {
		if (aboutDialog == null) {
			aboutDialog = new JDialog(applicationView, CommonString.ABOUT);
			JPanel aboutPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
			String about = CommonString.APOS + "\n" + "\n Version: 1.0"
					+ "\n\n" + CommonString.COPYRIGHT
					+ "Copyright Team-36 2012. All rights reserved."
					+ "\nCS 157A - San Jose State University." + "\n\n"
					+ "Contributors: " + "\n" + "Hiep Hong" + "\n" + "Nam Phan"
					+ "\n" + "Zi Yang" + "\n" + "Swathi" + "\n"
					+ "Avi Mukherrieg";
			JTextArea aboutTextArea = new JTextArea(about);
			aboutTextArea.setEditable(false);
			aboutTextArea.setBackground(aboutPanel.getBackground());

			aboutPanel.add(aboutTextArea);
			aboutPanel.setBorder(new BevelBorder(BevelBorder.RAISED));
			aboutDialog.setPreferredSize(new Dimension(300, 280));
			aboutDialog.add(aboutPanel);
		}

		UiUtil.centerAndShow(aboutDialog);
	}

	@SuppressWarnings("rawtypes")
	public Set<String> getAllModelNamesFor(String selectedMake) {
		Set<String> modelNames = new HashSet<String>();
		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		try {
			List loadAll = carDao.loadAll(connection);
			for (Object obj : loadAll) {
				AplCar car = (AplCar) obj;
				modelNames.add(car.getModel());
			}
		} catch (SQLException e) {
			JOptionPane
					.showMessageDialog(
							applicationView,
							"No models available for the selected make.\nChoose a different make.",
							"No Models Found", JOptionPane.ERROR_MESSAGE);
		}

		return modelNames;
	}

	private String getTableName(String selectedMake) {
		String code = null;
		for (Make make : allMakesCache) {
			if (make.getName().equals(selectedMake)) {
				code = make.getCode();
				break;
			}
		}

		String tableName = CommonString.AUTO_TABLE_PREFIX + code;
		return tableName;
	}

	@SuppressWarnings("rawtypes")
	public Set<String> getAllYearsFor(String selectedMake, String selectedModel) {
		Set<String> years = new HashSet<String>();
		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		AplCar sample = new AplCar(selectedModel, null, null, null, null, null);

		try {
			List matches = carDao.searchMatching(connection, sample);
			for (Object obj : matches) {
				AplCar car = (AplCar) obj;
				years.add(car.getYear());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return years;
	}

	@SuppressWarnings("rawtypes")
	public Set<String> getAllEnginesFor(String selectedMake,
			String selectedModel, String selectedYear) {
		Set<String> engines = new HashSet<String>();
		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		AplCar sample = new AplCar(selectedModel, selectedYear, null, null,
				null, null);

		try {
			List matches = carDao.searchMatching(connection, sample);
			for (Object obj : matches) {
				AplCar car = (AplCar) obj;
				engines.add(car.getEngineType());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return engines;
	}

	public void showPartListForAuto(String selectedMake, AplCar selectedCar) {
		if (selectedCar == null || selectedCar == null) {
			return;
		}

		selectingCar = true;
		this.selectedMake = selectedMake;
		this.selectedCar = selectedCar;

		int rlink;

		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		List<RdiPart> parts = new ArrayList<RdiPart>();

		try {
			List matching = carDao.searchMatching(connection, selectedCar);
			for (Object obj : matching) {
				AplCar car = (AplCar) obj;
				rlink = car.getRlink();
				Radcrx radcrx = radDao.getObject(connection, rlink);
				parts.addAll(getParts(radcrx));
			}

			if (parts.size() == 0) {
				showNoPartAvailableMessage();
			}

			partSummaryPanel.update(parts);

		} catch (NotFoundException e) {
			showNoPartAvailableMessage();
			// e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}

	@SuppressWarnings("rawtypes")
	public void showPartListForVendor(String selectedVendor) {
		if (selectedVendor.equals(CommonString.BLANK_SPACE)) {
			return;
		}
		selectingCar = false;
		this.selectedVendor = selectedVendor;

		partDao.setTable(selectedVendor);
		try {
			List allParts = partDao.loadAll(connection);
			List<RdiPart> parts = new ArrayList<RdiPart>();
			for (Object obj : allParts) {
				parts.add((RdiPart) obj);
			}

			if (parts.size() == 0) {
				showNoPartAvailableMessage();
			}

			partSummaryPanel.update(parts);

		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void showNoPartAvailableMessage() {
		JOptionPane.showMessageDialog(applicationView,
				"No parts available for the selected model",
				"No Parts Available", JOptionPane.INFORMATION_MESSAGE);
	}

	private List<RdiPart> getParts(Radcrx radcrx) {
		List<RdiPart> parts = new ArrayList<RdiPart>();

		Connection conn = connection;

		// Handle RDIMARX table.
		getArsParts(radcrx, parts, conn);

		// Handle RDIMBEH table.
		getBehParts(radcrx, parts, conn);

		// Handle RDIMDAN table.
		getDanParts(radcrx, parts, conn);

		// Handle RDIMMOD table.
		getModParts(radcrx, parts, conn);

		return parts;
	}

	private void getArsParts(Radcrx radcrx, List<RdiPart> parts, Connection conn) {
		String vendor = CommonString.RDIM_ARS;
		partDao.setTable(vendor);
		try {
			ArrayList<String> arsList = radcrx.getArsList();
			for (int i = 0; i < arsList.size(); i++) {
				if (arsList.get(i) == null) {
					continue;
				}

				RdiPart rdiPart = partDao.getObject(conn, arsList.get(i));
				if (rdiPart != null) {
					PartType type = PartType.values()[i];
					rdiPart.setType(type);
					parts.add(rdiPart);
				}

			}

		} catch (NotFoundException e) {
			if (radcrx.getArsList().isEmpty())
				System.out.print("No parts found for " + vendor);
			// e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void getBehParts(Radcrx radcrx, List<RdiPart> parts, Connection conn) {
		String vendor = CommonString.RDIM_BEH;
		partDao.setTable(vendor);
		try {
			ArrayList<String> behList = radcrx.getBehList();
			for (int i = 0; i < behList.size(); i++) {
				if (behList.get(i) == null) {
					continue;
				}

				RdiPart rdiPart = partDao.getObject(conn, behList.get(i));
				if (rdiPart != null) {
					PartType type = PartType.values()[i];
					rdiPart.setType(type);
					parts.add(rdiPart);
				}

			}

		} catch (NotFoundException e) {
			if (radcrx.getBehList().isEmpty())
				System.out.print("No parts found for " + vendor);
			// e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void getDanParts(Radcrx radcrx, List<RdiPart> parts, Connection conn) {
		String vendor = CommonString.RDIM_DAN;
		partDao.setTable(vendor);
		try {
			ArrayList<String> danList = radcrx.getDanList();
			for (int i = 0; i < danList.size(); i++) {
				if (danList.get(i) == null) {
					continue;
				}

				RdiPart rdiPart = partDao.getObject(conn, danList.get(i));
				if (rdiPart != null) {
					PartType type = PartType.values()[i];
					rdiPart.setType(type);
					parts.add(rdiPart);
				}

			}

		} catch (NotFoundException e) {
			if (radcrx.getDanList().isEmpty())
				System.out.print("No parts found for " + vendor);
			// e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void getModParts(Radcrx radcrx, List<RdiPart> parts, Connection conn) {
		String vendor = CommonString.RDIM_MOD;
		partDao.setTable(vendor);
		try {
			ArrayList<String> modList = radcrx.getModList();
			for (int i = 0; i < modList.size(); i++) {
				if (modList.get(i) == null) {
					continue;
				}

				RdiPart rdiPart = partDao.getObject(conn, modList.get(i));
				if (rdiPart != null) {
					PartType type = PartType.values()[i];
					rdiPart.setType(type);
					parts.add(rdiPart);
				}

			}

		} catch (NotFoundException e) {
			if (radcrx.getModList().isEmpty())
				System.out.print("No parts found for " + vendor);
			// e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public void orderPart(RdiPart selectedPart) {
		int response = JOptionPane.showConfirmDialog(applicationView,
				"Proceed with ordering the selected part?",
				"Order Part Confirmation", JOptionPane.YES_NO_OPTION);

		// Proceed with ordering.
		if (response == JOptionPane.YES_OPTION) {
			System.out.print("\nPart ordered");
		}

	}

	public void editPart(RdiPart selectedPart) {
		System.out.print("\nEdit part");
	}

	public String getNextMakerNo() {
		String res = null;
		try {
			res = makeDao.getNextMakerNo(connection) + "";
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return res;
	}

	public void saveMaker(Make maker) {
		try {
			makeDao.create(connection, maker);
			refreshFilterPanel();
			reset();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public List getAllRlink() {

		try {
			RadcrxDao radDao = new RadcrxDao();
			return radDao.getAllRlink(connection);

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return null;
	}

	public List getAllMakeCode() {
		try {
			return makeDao.getAllMakeCode(connection);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

	public String getMakeNameOfCode(String code) {
		try {
			return makeDao.searchName(connection, code);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

	public void setAutoFilterPanel(AutoFilterPanel autoFilterPanel) {
		this.autoFilterPanel = autoFilterPanel;
	}

	private void refreshFilterPanel() {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				List<String> allMakeNames = getAllMakeNames();
				autoFilterPanel.setMakes(allMakeNames);
			}
		});

	}

	/**
	 * This class listens for the event when user click on the "X" button at the
	 * upper right corner of the main application window.
	 * 
	 * The purpose is to properly exit the application e.g. clean up resources,
	 * disconnect from the database, etc.
	 * 
	 * @author Hiep
	 * 
	 */
	private final class MainWindowListener implements WindowListener {

		@Override
		public void windowOpened(WindowEvent arg0) {
			// Ignore.
		}

		@Override
		public void windowIconified(WindowEvent arg0) {
			// Ignore.
		}

		@Override
		public void windowDeiconified(WindowEvent arg0) {
			// Ignore.
		}

		@Override
		public void windowDeactivated(WindowEvent arg0) {
			// Ignore.
		}

		@Override
		public void windowClosing(WindowEvent arg0) {
			// Perform proper exiting operation.
			exit();
		}

		@Override
		public void windowClosed(WindowEvent arg0) {
			// Ignore.
		}

		@Override
		public void windowActivated(WindowEvent arg0) {
			// Ignore.
		}
	}

	public void partSelectedPerformed(RdiPart selectedPart) {
		if (selectedPart == null) {
			return;
		}
		partDetailPanel.display(selectedPart);
	}

	public void updatePart(RdiPart oldPart, RdiPart part) {
		// TODO Auto-generated method stub
		int response = JOptionPane.showConfirmDialog(applicationView,
				"Are you sure you want to update the selected part?",
				"Update Part Confirmation", JOptionPane.YES_NO_OPTION);

		// Proceed with ordering.
		if (response == JOptionPane.YES_OPTION) {
			System.out.print("\nPart updated");
			try {
				partDao.save(connection, oldPart, part);
				refresh();
			} catch (NotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	public void savePart(RdiPart part) {
		// TODO Auto-generated method stub
		try {
			partDao.create(connection, part);
			refresh();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public Set<String> getAllDescriptionsForCar(String selectedMake,
			AplCar selectedCar) {

		Set<String> descriptions = new HashSet<String>();

		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		try {
			List matching = carDao.searchMatching(connection, selectedCar);
			for (Object obj : matching) {
				AplCar car = (AplCar) obj;
				descriptions.add(car.getDescription());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return descriptions;
	}

	public Set<String> getAllLitersForCar(String selectedMake,
			AplCar selectedCar) {
		Set<String> liters = new HashSet<String>();

		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		try {
			List matching = carDao.searchMatching(connection, selectedCar);
			for (Object obj : matching) {
				AplCar car = (AplCar) obj;
				liters.add(car.getLitres());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return liters;

	}

	public Set<String> getAllCubicsForCar(String selectedMake,
			AplCar selectedCar) {
		Set<String> cubics = new HashSet<String>();

		String tableName = getTableName(selectedMake);
		carDao.setTable(tableName);

		try {
			List matching = carDao.searchMatching(connection, selectedCar);
			for (Object obj : matching) {
				AplCar car = (AplCar) obj;
				cubics.add(car.getCubicInches());
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}

		return cubics;

	}

	public void deletePart(RdiPart selectedPart) {

		try {
			int response = JOptionPane.showConfirmDialog(applicationView,
					"Are you sure you want to delete the selected part?",
					"Delete Confirmation", JOptionPane.YES_NO_OPTION);

			// Proceed with deleting.
			if (response == JOptionPane.YES_OPTION) {
				System.out.print("\nPart Deleted.");
				System.out.println("DELETE: " + selectedPart.getPartNumber());
				partDao.delete(connection, selectedPart);
				refresh();
			}

		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void newMakerPerformed() {
		if (newMakerDialog == null) {
			newMakerDialog = new NewMakerDialog(this);
			newMakerDialog.setTitle(CommonString.NEW_MAKER);
		}

		UiUtil.centerAndShow(newMakerDialog);
	}

	public void newPartPerformed() {
		if (newPartDialog == null) {
			newPartDialog = new NewPartDialog(this);
			newPartDialog.setTitle(CommonString.NEW_PART);
		}

		UiUtil.centerAndShow(newPartDialog);
	}

	public void newAutoModelPerformed() {
		if (newAutoModelDialog == null) {
			newAutoModelDialog = new NewAutoModelDialog(this);
			newAutoModelDialog.setTitle(CommonString.NEW_AUTO_MODEL);
		}

		UiUtil.centerAndShow(newAutoModelDialog);

	}

	public void saveAutoModel(AplCar autoModel, String maker, Radcrx radcrx) {
		// TODO Auto-generated method stub
		try {
			int rlink = radDao.getNextRlink(connection);
			radcrx.setRlink(rlink);
			radDao.create(connection, radcrx);

			autoModel.setRlink(rlink);
			carDao.create(connection, autoModel, maker);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void reset() {
		partSummaryPanel.reset();
		partDetailPanel.reset();

	}

	public List<RdiPart> getAllParts() {
		List<RdiPart> parts = new ArrayList<RdiPart>();

		for (VendorType type : VendorType.values()) {
			partDao.setTable(type.toString());
			List<Object> allParts;
			try {
				allParts = partDao.loadAll(connection);
				for (Object obj : allParts) {
					RdiPart p = (RdiPart) obj;

					for (PartType partType : PartType.values()) {
						RdiPart clone = (RdiPart) p.clone();
						clone.setType(partType);
						parts.add(clone);
					}

				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

		return parts;
	}

	public void refresh() {
		// TODO Auto-generated method stub
		if (selectingCar) {
			showPartListForAuto(selectedMake, selectedCar);
		} else {
			showPartListForVendor(selectedVendor);
		}

		partDetailPanel.reset();

	}

	public List<String> getAllPartNumbersFor(String vendor) {
		// TODO Auto-generated method stub
		List<String> partNumbers = new ArrayList<String>();
		partDao.setTable(vendor);
		try {
			List all = partDao.loadAll(connection);
			for (Object obj : all) {
				RdiPart part = (RdiPart) obj;
				partNumbers.add(part.getPartNumber().trim());
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return partNumbers;
	}

}
